/**
 *  Serponix is an arcade game in focus to multiplayer based on the classic game Snake.
 *  Copyright (C) 2010 - 2011  Daniel Vala
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License,
 *  or  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.

 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  If you have any question do not hesitate to contact author
 *  on e-mail address: danielvala42@gmail.com
 */
package com.serponix.game;

import com.serponix.abilities.*;
import com.serponix.game.food.Cake;
import com.serponix.game.food.Cherry;
import com.serponix.game.projectiles.Projectile;
import com.serponix.game.score.Score;
import com.serponix.gui.WindowInterface;
import com.serponix.net.Informace;
import com.serponix.net.Server;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import javax.swing.JLabel;

/**
 * Dědí z hlavního modelu hry, reprezentuje Fun mód.
 *
 * @author Daniel Vala
 */
public final class ModelFun extends GameModel {

	private List<Ability> seznamVlastnosti;
	private int vyskytBonusu = 3;
	private int sanceVlastnostiCelkem;

	/**
	 * Vytvoří model hry pro mód Fun.
	 *
	 * @param okno         Interface okna pro přístup k ovládání celé hry (např. přístup zpět do menu)
	 * @param parametryHry Parametry hry
	 * @param skoreStitky  Štítky, na které se bude psát skóre všech hráčů.
	 * @param panelZprav   Panel zpráv, kam se budou psát zprávy o nových událostech.
	 */
	public ModelFun(WindowInterface okno, GameParameters parametryHry, GamingArea herniPanel, JLabel[] skoreStitky, MessagePanel panelZprav) {
		super(okno, parametryHry, herniPanel, skoreStitky, panelZprav, parametryHry.getSizeOfGamingArea());
		parametryHry.getServer().setModel(this);
		net = parametryHry.getServer();
		hadi = new Snake[getPocetHadu()];
		vytvorListPocatecnichPozic(10);
		Collections.shuffle(listPocatecnichPozic);
		for (int i = 0; i < hadi.length; i++) {
			hadi[i] = new Snake(this, getHrac(i), listPocatecnichPozic.get(i), -1, Direction.DOWN, 3, getVelikostPolicka(), 12, 42, barvy[i], null, true, true);
			getHrac(i).init(this, hadi[i]);
		}
		jidlo = new Cake(this);
		initVlastnosti();

		startHry();
	}

	/**
	 * Inicializace všech bonusových vlastností, které mohou být vygenerovány při sebrání bonusu.
	 * Každá vlastnost má danou pravděpodobnost vygenerování se.
	 */
	private void initVlastnosti() {
		seznamVlastnosti = new ArrayList<Ability>();

		ZasobnikLaseru zasobnikyLaseru = new ZasobnikLaseru(100);
		Rakety rakety = new Rakety(70);
		Jed jed = new Jed(40);
		Tank tank = new Tank(50);
		Steak steak = new Steak(40);
		Tabletka tabletka = new Tabletka(45);
		Pivo pivo = new Pivo(20);
		ElixirZivota elixirZivota = new ElixirZivota(30);
		Stit stit = new Stit(37);
		Turtle turtle = new Turtle(70);
		Lopata lopata = new Lopata(60);
		Duch duch = new Duch(26);
		Swap swap = new Swap(34);
		Vesta vesta = new Vesta(30);
		Pstros pstros = new Pstros(60);
		StrojCasu strojCasu = new StrojCasu(34);

		seznamVlastnosti.add(zasobnikyLaseru);
		seznamVlastnosti.add(rakety);
		seznamVlastnosti.add(jed);
		seznamVlastnosti.add(tank);
		seznamVlastnosti.add(steak);
		seznamVlastnosti.add(tabletka);
		seznamVlastnosti.add(pivo);
		seznamVlastnosti.add(elixirZivota);
		seznamVlastnosti.add(stit);
		seznamVlastnosti.add(turtle);
		seznamVlastnosti.add(lopata);
		seznamVlastnosti.add(duch);
		seznamVlastnosti.add(swap);
		seznamVlastnosti.add(vesta);
		seznamVlastnosti.add(pstros);
		seznamVlastnosti.add(strojCasu);

		// součet všech vlastností celkem
		for (Ability vlastnost : seznamVlastnosti) {
			sanceVlastnostiCelkem += vlastnost.getSance();
		}

		// nastavení pravděpodobnosti každé vlastnosti
		for (Ability vlastnost : seznamVlastnosti) {
			vlastnost.setPravdepodobnost(vlastnost.getSance() / new Double(sanceVlastnostiCelkem));
		}

		// výpis pravděpodobností všech bonusů v procentech
		//        for (Vlastnost vlastnost : seznamVlastnosti) {
		//            System.out.println("Vlastnost " + vlastnost.getClass() + " sance v procentech je: " + (Math.round(vlastnost.getPravddepodobnost() * 10000) / new Double(100)) + "%");
		//        }
	}

	/**
	 * Vygeneruje náhodnou vlastnost, přičemž každá
	 * vlastnost má svou různou pravděpodobnost vygenerování.
	 *
	 * @param had Had, který generuje vlastnost.
	 */
	public void vygenerujVlastnost(Snake had) {
		// podařilo se vygenerovat bonus?
		boolean uspech = false;
		int i = 0;
		// generuj vlastnost, dokud se nějakou úspěšně nepodaří vygenerovat
		// nebo počet generování nepřesáhne 1000
		do {
			int nahoda = 1 + generatorNahody.nextInt(sanceVlastnostiCelkem);
			for (Ability vlastnost : seznamVlastnosti) {
				if (nahoda - vlastnost.getSance() < 0) {
					uspech = vlastnost.proved(this, had);
					break;
				}
				nahoda -= vlastnost.getSance();
			}
			i++;
			if (i > 1000) {
				getPanelZprav().addMessage("Žádnou vlastnost se nepodařilo vygenerovat.");
				return;
			}
		} while (!uspech);
	}

	/**
	 * Zjistí, zda má některý z hadů vlastnost Tron.
	 *
	 * @return true, pokud je některý z hadů tronem. Jinak false.
	 */
	public boolean isTron() {
		for (int i = 0; i < getPocetHadu(); i++) {
			if (hadi[i].isTron()) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Posune hada na náhodnou startovní pozici a zkrátí ho o jednu kostičku.
	 */
	@Override
	public void resetHada(Snake had) {
		// pokud je had tronem, zabije se
		if (had.isTron()) {
			had.smrt();
			return;
		}
		// had se nastaví na náhodné startovní pozici a zkrátí se o 1 článek
		Collections.shuffle(listPocatecnichPozic);
		had.zkratHada(1);
		had.setHad(listPocatecnichPozic.get(0), -1, Direction.DOWN);
	}

	/**
	 * Upozorní model, že je potřeba inicializovat nové kolo hry.
	 * Inicializace neproběhne ihned, ale až po skončení kroku hry.
	 */
	@Override
	public void newRound() {
		newRound = true;
	}

	/**
	 * Inicializuje nové kolo hry. Všechno se nastaví do defaultního stavu pro začátek nového kola.
	 * Tzn. všichni hadi začnou na náhodných pozicích směrem dolu po vteřině čekání v základní velikosti a bez žádných vlastností.
	 * Vygeneruje se nové jídlo, případně se smaže bonus.
	 */
	private void initNewRound() {
		Collections.shuffle(listPocatecnichPozic);
		Snake[] listHadu = getHadi();
		for (int i = 0; i < getPocetHadu(); i++) {
			listHadu[i].setHad(listPocatecnichPozic.get(i), -1, Direction.DOWN);
		}
		odstranVsechnyStrely();
		jidlo.odmapuj(this);
		jidlo.generateFood();
		if (bonus != null) {
			bonus.odmapuj(this);
			bonus = null;
		}
		pockej(1000);
		ozivVsechnyHady();
		newRound = false;
	}

	/**
	 * Skore se v tomto modelu nepoužívá.
	 *
	 * @param skore
	 * @param body
	 */
	@Override
	public void addScore(Score skore, int body) {
	}

	/**
	 * Zjistí, zda jsou všichni hadi naživu.
	 *
	 * @return True pokud jsou všichni naživu. False, pokud je alespoň jeden had mrtev.
	 */
	private boolean isVsichniHadiNazivu() {
		for (int i = 0; i < hadi.length; i++) {
			if (!hadi[i].isAlive()) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Pokud je alespoň jeden had ve hře mrtev, s velkou pravděpodobností jej oživí.
	 * Pokud je mrtvých hadů více, využije náhody.
	 *
	 * @param had Had, který oživuje jiného hada.
	 * @return True, pokud byl nějáký had oživen. Jinak false.
	 */
	public boolean ozivNahodnehoHada(Snake had) {
		if (isVsichniHadiNazivu()) {
			return false;
		}
		for (int i = 0; i < 300; i++) { // 300 pokusů na oživení hada
			int a = generatorNahody.nextInt(hadi.length);
			if (!hadi[a].isAlive()) {
				hadi[a].oziv();
				getPanelZprav().addMessage(String.format("%s oživil %s ", had, hadi[a]));
				return true;
			}
		}
		return false;
	}

	@Override
	public void jidloSezrano(Snake had) {
		had.prodluzHada();
		jidlo.generateFood();
		int stesti = 1 + generatorNahody.nextInt(vyskytBonusu);
		if (stesti == 1 && bonus == null) {
			bonus = new Cherry(this);
		}
	}

	@Override
	public void bonusSezran(Snake had) {
		vygenerujVlastnost(had);
		Toolkit.getDefaultToolkit().beep();
		bonus.odmapuj(this);
		bonus = null;
	}

	/**
	 * Reprezentuje tzv. krok celé hry, posune živé hady, aktivní střely, připadně vygeneruje jídlo a bonus.
	 * Aktualizuje skore štítky, pošle data klientům a informuje o nutnosti překreslit plátno.
	 */
	@Override
	public void krok() {
		//        List<Strela> kopieStrel = new ArrayList<Strela>();
		//        for (Strela strela : strely) {
		//            kopieStrel.add(strela);
		//        }

		//        Iterator<Strela> it = kopieStrel.iterator();
		//        while (it.hasNext()) {
		//            it.next().posunSe();
		//        }

		// posun všech střel, střely se při pohybu nesmí sami mazat kvůli concurentModificationException
		// pokud byli při pohybu zničeny, metoda posunSe vrátí false. Iterator je poté smaže sám.
		Iterator<Projectile> it = strely.iterator();
		while (it.hasNext()) {
			if (!it.next().posunSe()) {
				it.remove();
			}
		}

		//        for (int i = 0; i < strely.size(); i++) {
		//            strely.get(i).posunSe();
		//        }

		for (int i = 0; i < getPocetHadu(); i++) {
			getHrac(i).posunSe();
			setAllSkoreText(i, getHrac(i).getJmeno() + ":" + hadi[i].score.getPocetVyher() + " ");

			if (hadi[i].isVlastnostNeviditelnost() && getHrac(i).getPacket() != null) {
				posliHada(hadi[i]);
			}
		}

		// pokud během kroku nastali události, jež vyvolali potřebu nového kola, začne se nové kolo
		if (newRound) {
			initNewRound();
		}

		sendAllDataToClients();

		setChanged();
		notifyObservers();
	}

	private void sendAllDataToClients() {
		posliPole();
		posliStavStitku();
		posliInfo();
		posliKrok();
	}

	/**
	 * Pošle instrukce pro kreslení všech políček na herní ploše.
	 */
	public void posliPole() {
		String zpravaKOdeslani = Informace.POLICKO + Informace.ODDELOVAC1;
		// přidání všech políček do zprávy
		if (jidlo != null) {
			zpravaKOdeslani += jidlo.getInstrukceProKresleni();
		}
		if (bonus != null) {
			zpravaKOdeslani += bonus.getInstrukceProKresleni();
		}
		for (int j = 0; j < getPocetHadu(); j++) {
			zpravaKOdeslani += hadi[j].getInstrukceProKresleni(false);
		}

		for (int i = 0; i < strely.size(); i++) {
			zpravaKOdeslani += strely.get(i).getInstrukceProKresleni();
		}

		net.poslatData(zpravaKOdeslani);
	}

	/**
	 * Pošle informaci, že má klient uskutečnit další krok hry.
	 */
	public void posliKrok() {
		String zpravaKOdeslani = Informace.KROK + Informace.ODDELOVAC1;
		net.poslatData(zpravaKOdeslani);
	}

	public void posliStavStitku() {
		String zpravaKOdeslani = Informace.SKORE + Informace.ODDELOVAC1;

		// přidání textů všech štítků do zprávy
		for (int i = 0; i < getPocetHadu(); i++) {
			zpravaKOdeslani += getSkoreText(i) + Informace.ODDELOVAC3;
		}
		net.poslatData(zpravaKOdeslani);
	}

	/**
	 * Pošle nové zprávy do panelu zpráv klientům.
	 */
	public void posliInfo() {
		String zpravaKOdeslani = getPanelZprav().getMessageToSend();
		if (zpravaKOdeslani != null) {
			net.poslatData(zpravaKOdeslani);
		}
	}

	/**
	 * Pošle informace pro kreslení daného hada klientovi, který ho vlastní.
	 *
	 * @param had Had, kterému se budou posílat instrukce pro kreslení jeho hada.
	 */
	public void posliHada(Snake had) {
		String zpravaKOdeslani = Informace.POLICKO + Informace.ODDELOVAC1;
		zpravaKOdeslani += had.getInstrukceProKresleni(true);
		((Server) net).posliDataKlientovi(zpravaKOdeslani, had.getHrac().getPacket());
	}

	/**
	 * Vykreslí mapu, hady a jídlo.
	 *
	 * @param g
	 */
	public void paint(Graphics g) {
		if (!zobrazVeciNaPlose) {

			for (Snake had : hadi) {
				had.draw(g);
			}
			for (int i = 0; i < strely.size(); i++) {
				strely.get(i).draw(g);
			}
			//            for (Projectile strela : strely) {
			//                strela.draw(g);
			//            } // TODO concurentModificationException
			jidlo.draw(g);
			if (bonus != null) {
				bonus.draw(g);
			}
		} else {
			zobrazVeciNaPlose(g);
		}
	}

	/**
	 * Vykreslí zdi mapy.
	 *
	 * @param g Grafický objekt pro kreslení.
	 */
	public void paintZdi(Graphics g) {
		if (!zobrazVeciNaPlose) {
			getMapa().kresliMapu(g);
		}
	}
}
